﻿using System;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using TTprotons.Crm.JavascriptAudit.Common;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;

namespace TTprotons.Crm.JavascriptAudit
{
    public class JavascriptAuditPlugin : IPlugin
    {
        private readonly List<string> allowableEntities;
        private readonly bool allowAllEntities;

        public JavascriptAuditPlugin(string unsecure, string secure)
        {
            allowableEntities = new List<string>();

            var xml = XElement.Parse(unsecure);
            var allowNodes = xml.Descendants("allow");

            if (allowNodes != null)
            {
                foreach (var allowNode in allowNodes)
                {
                    if (allowNode.Value.Equals("*"))
                    {
                        allowAllEntities = true;
                        allowableEntities.Clear();
                        return;
                    }

                    allowableEntities.Add(allowNode.Value);
                }
            }
        }

        #region IPlugin Members

        public void Execute(IPluginExecutionContext context)
        {
            if (!context.InputParameters.Contains(Constants.ParameterXml))
                return;

            var parameterXml = context.InputParameters[Constants.ParameterXml].ToString();
            var parser = new ParameterXmlParser(parameterXml);
            var entityName = parser.EntityName;

            // Verify if this is the allowable entities
            if (!IsEntityAllowed(entityName))
                return;
            
            // Setup the ContextHelper and CrmService.
            var contextHelper = new ContextHelper(context);
            var crmService = contextHelper.CrmService;

            if (context.SharedVariables.Contains(Constants.PrePublishCustomization) && context.SharedVariables.Contains(Constants.PostPublishCustomization))
            {
                try
                {
                    var preCustomizationFile = (byte[])context.SharedVariables[Constants.PrePublishCustomization];
                    var postCustomizationFile = (byte[])context.SharedVariables[Constants.PostPublishCustomization];

                    PublishedOn = (DateTime)context.SharedVariables[Constants.PublishedOn];

                    // Compare the Javascript between pre and post customization files.
                    CompareJavascript(crmService, preCustomizationFile, postCustomizationFile);
                }
                catch (Exception)
                { }
            }
        }

        private bool IsEntityAllowed(string entityName)
        {
            return allowAllEntities || allowableEntities.Contains(entityName);
        }

        #endregion

        #region Properties

        protected DateTime PublishedOn
        { get; set; }

        #endregion

        private void CompareJavascript(ICrmService crmService, byte[] preCustomizationFile, byte[] postCustomizationFile)
        {
            Log("About to compare");

            var preCustomizationXml = ZipStreamHelper.ExtractTextFile(preCustomizationFile, Constants.CrmCustomizationFileName, Encoding.UTF8);
            var preCustomizationParser = new ImportExportXmlParser(preCustomizationXml);

            if (!preCustomizationParser.IsSuccess)
                return;

            var postCustomizationXml = ZipStreamHelper.ExtractTextFile(postCustomizationFile, Constants.CrmCustomizationFileName, Encoding.UTF8);
            var postCustomizationParser = new ImportExportXmlParser(postCustomizationXml);

            if (!postCustomizationParser.IsSuccess)
                return;

            var preJavascripts = preCustomizationParser.Javascripts;
            var postJavascripts = postCustomizationParser.Javascripts;

            var jsAuditLogs = new List<JavascriptAuditLog>();

            if (!preJavascripts.IsNullOrEmpty())
            {
                foreach (var preJavascript in preJavascripts)
                {
                    JavascriptEvent relevantPostJS = null;
                    if (!postJavascripts.IsNullOrEmpty())
                        relevantPostJS = postJavascripts.FirstOrDefault(i => i.Equals(preJavascript));

                    var newAuditLog = new JavascriptAuditLog
                                          {
                                              EntityName = preJavascript.EntityName,
                                              EventName = preJavascript.EventName,
                                              FieldLogicalName = preJavascript.FieldLogicalName,
                                              FieldName = preJavascript.FieldName,
                                              FormType = preJavascript.FormType,
                                              IsActive = preJavascript.IsActive,
                                              JavascriptType = preJavascript.JavascriptType,
                                              PreJavascriptCode = preJavascript.JavascriptCode,
                                              PublishedOn = PublishedOn
                                          };

                    if (relevantPostJS != null)
                    {
                        // Remove the one that's already been processed
                        postJavascripts.Remove(relevantPostJS);

                        if (relevantPostJS.JavascriptCode.Equals(preJavascript.JavascriptCode) && relevantPostJS.IsActive.Equals(preJavascript.IsActive))
                            continue;

                        newAuditLog.PostJavascriptCode = relevantPostJS.JavascriptCode;
                        newAuditLog.IsActive = relevantPostJS.IsActive;
                    }
                    else
                    {
                        // The javascript got removed, therefore, no longer active
                        newAuditLog.IsActive = false;
                    }

                    jsAuditLogs.Add(newAuditLog);
                }
            }

            if (!postJavascripts.IsNullOrEmpty())
            {
                foreach (var postJavascript in postJavascripts)
                {
                    var newAuditLog = new JavascriptAuditLog
                    {
                        EntityName = postJavascript.EntityName,
                        EventName = postJavascript.EventName,
                        FieldLogicalName = postJavascript.FieldLogicalName,
                        FieldName = postJavascript.FieldName,
                        FormType = postJavascript.FormType,
                        IsActive = postJavascript.IsActive,
                        JavascriptType = postJavascript.JavascriptType,
                        PostJavascriptCode = postJavascript.JavascriptCode,
                        PublishedOn = PublishedOn
                    };

                    jsAuditLogs.Add(newAuditLog);
                }
            }

            SaveJavascriptAuditLogs(crmService, jsAuditLogs);
        }

        private static void SaveJavascriptAuditLogs(ICrmService crmService, IEnumerable<JavascriptAuditLog> jsAuditLogs)
        {
            if (jsAuditLogs.IsNullOrEmpty()) return;

            foreach (var javascriptAuditLog in jsAuditLogs)
            {
                try
                {
                    var entity = new DynamicEntity(Constants.JavascriptAuditEntity) { Properties = new PropertyCollection() };
                    entity.Properties.Add(new StringProperty("ttp_entityname", javascriptAuditLog.EntityName.Norm()));
                    entity.Properties.Add(new StringProperty("ttp_eventname", javascriptAuditLog.EventName.Norm()));
                    entity.Properties.Add(new StringProperty("ttp_fieldlogicalname", javascriptAuditLog.FieldLogicalName.Norm()));
                    entity.Properties.Add(new StringProperty("ttp_fieldname", javascriptAuditLog.FieldName.Norm()));
                    entity.Properties.Add(new PicklistProperty("ttp_formtype", new Picklist((int)javascriptAuditLog.FormType)));
                    entity.Properties.Add(new CrmBooleanProperty("ttp_isactive", new CrmBoolean(javascriptAuditLog.IsActive)));
                    entity.Properties.Add(new PicklistProperty("ttp_javascripttype", new Picklist((int)javascriptAuditLog.JavascriptType)));
                    entity.Properties.Add(new CrmDateTimeProperty("ttp_publishedon", new CrmDateTime(javascriptAuditLog.PublishedOn.ToString("s"))));

                    if (javascriptAuditLog.JavascriptType == JavascriptTypes.Form)
                        entity.Properties.Add(new StringProperty("ttp_name", string.Format("{0} Form script ({1}) updated on {2}", javascriptAuditLog.EntityName, javascriptAuditLog.EventName, javascriptAuditLog.PublishedOn)));
                    else
                        entity.Properties.Add(new StringProperty("ttp_name", string.Format("{0} Field script ({1} - {2}) updated on {3}", javascriptAuditLog.EntityName, javascriptAuditLog.FieldLogicalName, javascriptAuditLog.EventName, javascriptAuditLog.PublishedOn)));

                    var logId = crmService.Create(entity);

                    var postNote = new annotation
                    {
                        subject = "Current Javascript",
                        notetext = javascriptAuditLog.PostJavascriptCode,
                        objectid = new Lookup(Constants.JavascriptAuditEntity, logId),
                        objecttypecode = new EntityNameReference(Constants.JavascriptAuditEntity)
                    };

                    var preNote = new annotation
                    {
                        subject = "Previous Javascript",
                        notetext = javascriptAuditLog.PreJavascriptCode,
                        objectid = new Lookup(Constants.JavascriptAuditEntity, logId),
                        objecttypecode = new EntityNameReference(Constants.JavascriptAuditEntity)
                    };

                    crmService.Create(postNote);
                    crmService.Create(preNote);
                }
                catch (Exception)
                { }
            }
        }

        private void Log(string logMessage)
        {
            File.WriteAllText(@"C:\Windows\temp\result.txt", logMessage + Environment.NewLine);
        }
    }
}